package krillr.Util;
import robocode.*;
import java.util.*;
import java.util.zip.*;
import java.io.*;
import java.awt.*;

public class KrillBot extends TeamRobot
{    
    /* A boolean to say whether or not we are plaing */
    public boolean playing = false;
    
    public ArrayList moduleList = new ArrayList();
    public ArrayList shapeList = new ArrayList();
    public BattlefieldTracker fieldTracker;
    public UnitTracker target;
    
    public double _trackAmount = 40.0;
    public double _trackBackAmount = 0;
    public boolean _scannedOnce = false;
    public boolean _won = false;
    public ScannedRobotEvent _lastEvent;
    
    double hits = 0;
    double fires = 0;
    
    /* This is called every time the bot hits a wall */
    public final void onHitWall(HitWallEvent e) {
        /* These functions tell our modules that we hit a wall, and hands over
         * the HitWallEvent for processing */
        Iterator it = moduleList.iterator();
        while(it.hasNext()) {
            Module m = (Module)it.next();
            m.onHitWall(e);            
        }
    }
    
    /* This is called every time the bot is hit by a bullet */
    public final void onHitByBullet(HitByBulletEvent e) {
        /* These functions tell our modules that we were hit, and hands over
         * the HitByBulletEvent for processing */
        Iterator it = moduleList.iterator();
        while(it.hasNext()) {
            Module m = (Module)it.next();
            m.onHitByBullet(e);            
        }
    }
    
    /* This is called every time we ran into a robot */
    public final void onHitRobot(HitRobotEvent e) {
        /* These functions tell our modules that we ran into a robot,
         * and hands over the MessageEvent for processing */
        Iterator it = moduleList.iterator();
        while(it.hasNext()) {
            Module m = (Module)it.next();
            m.onHitRobot(e);            
        }
    }
    
    /* This is called every time the bot sees another robot on the field */
    public final void onScannedRobot(ScannedRobotEvent e) {
        /* These functions tell our modules that we won, and hands over
         * the WinEvent for processing */
        fieldTracker.addUnit(e);
        Iterator it = moduleList.iterator();
        while(it.hasNext()) {
            Module m = (Module)it.next();
            m.onScannedRobot(e);            
        }
        _lastEvent = e;
        _scannedOnce = true;
        adjustRadar(e.getBearing());
    }
    
    /* This is called every time a robot on the field dies */
    public final void onBulletHit(BulletHitEvent e) {
        /* These functions tell our modules that a bot died, and hands over
         * the RobotDeathEvent for processing */
        hits++;
        Iterator it = moduleList.iterator();
        while(it.hasNext()) {
            Module m = (Module)it.next();
            m.onBulletHit(e);            
        }
    }
    
    /* This is called every time a robot on the field dies */
    public final void onBulletMissed(BulletMissedEvent e) {
        /* These functions tell our modules that a bot died, and hands over
         * the RobotDeathEvent for processing */
        Iterator it = moduleList.iterator();
        while(it.hasNext()) {
            Module m = (Module)it.next();
            m.onBulletMissed(e);            
        }
    }
    
    /* This is called every time a robot on the field dies */
    public final void onBulletHitBullet(BulletHitBulletEvent e) {
        /* These functions tell our modules that a bot died, and hands over
         * the RobotDeathEvent for processing */
        Iterator it = moduleList.iterator();
        while(it.hasNext()) {
            Module m = (Module)it.next();
            m.onBulletHitBullet(e);            
        }
    }
    
    /* This is called every time the bot wins a round */
    public final void onWin(WinEvent e) {
        /* These functions tell our modules that we won, and hands over
         * the WinEvent for processing */
        Iterator it = moduleList.iterator();
        while(it.hasNext()) {
            Module m = (Module)it.next();
            m.onWin(e);            
        }
        printStats();
        /* Stop the event loop */
        playing = false;
    }
    
    /* This is called every time our bot dies a round */
    public final void onDeath(DeathEvent e) {
        /* These functions tell our modules that we died, and hands over
         * the DeathEvent for processing */
        Iterator it = moduleList.iterator();
        while(it.hasNext()) {
            Module m = (Module)it.next();
            m.onDeath(e);            
        }
        HashMap units = fieldTracker.units();
        it = units.keySet().iterator();
        while(it.hasNext()) {
            UnitTracker unit = (UnitTracker)units.get(it.next());
        }
        printStats();
        /* Stop the event loop */
        playing = false;
    }
    
    /* This is called every time a bot dies */
    public final void onRobotDeath(RobotDeathEvent e) {
        String name = e.getName();
        fieldTracker.removeUnit(name);
        /* These functions tell our modules that we died, and hands over
         * the DeathEvent for processing */
        Iterator it = moduleList.iterator();
        while(it.hasNext()) {
            Module m = (Module)it.next();
            m.onRobotDeath(e);            
        }
    }
    
    /* This is called ever time a custom event returns true */
    public final void onCustomEvent(CustomEvent e) {
        /* These functions tell our modules that a custom event has occurred,
         * and hands over the CustomEvent for processing */
        Iterator it = moduleList.iterator();
        while(it.hasNext()) {
            Module m = (Module)it.next();
            m.onCustomEvent(e);            
        }
    }
    
    /* This is called every time a message is recieved from a teammate */
    public final void onMessageReceived(MessageEvent e) {
        /* These functions tell our modules that a message has been recieved,
         * and hands over the MessageEvent for processing */
        Iterator it = moduleList.iterator();
        while(it.hasNext()) {
            Module m = (Module)it.next();
            m.onMessageReceived(e);            
        }
    }
    
    public void onSkippedTurn(SkippedTurnEvent e) {
        /* These functions tell our modules that a message has been recieved,
         * and hands over the MessageEvent for processing */
        Iterator it = moduleList.iterator();
        while(it.hasNext()) {
            Module m = (Module)it.next();
            m.onSkippedTurn(e);
        }
        System.out.println("Turn skipped at: " + e.getTime());
    }
    
    public final UnitTracker getTarget() { return target; }
    public void setTarget(UnitTracker t) { target = t; }
    public UnitTracker findTarget() { return fieldTracker.getBestEnemy(); }
    
    public Vector2d getVector() {
        Vector2d myVector = new Vector2d();
        myVector.setDirection(getHeadingRadians());
        myVector.setMagnitude(getVelocity());
        return myVector;
    }
    
    public Vector2d getGunVector() {
        Vector2d myVector = new Vector2d();
        myVector.setDirection(getGunHeadingRadians());
        return myVector;
    }
    
    public Vector2d getRadarVector() {
        Vector2d myVector = new Vector2d();
        myVector.setDirection(getRadarHeadingRadians());
        return myVector;
    }
    
    public void setVector(Vector2d newVector) {
        double newSpeed = newVector.getMagnitude();
        double newAngle = KrillUtil.bestAngle(newVector.getDirection() - getHeadingRadians());
        if(Math.toDegrees(newAngle) < 10) 
            setMaxTurnRate(Math.toDegrees(newAngle));
        else
            setMaxTurnRate(10);
        setTurnRightRadians(newAngle);
        setMaxVelocity(newSpeed);
        setAhead(Double.POSITIVE_INFINITY);
    }
    
    public void setGunVector(Vector2d newVector) {
        double bulletEnergy = newVector.getMagnitude();
        double newAngle = KrillUtil.bestAngle(newVector.getDirection() - getGunHeadingRadians());
        setTurnGunRightRadians(newAngle);
        fires++;
        setFire(bulletEnergy);
    }
    
    public void setRadarVector(Vector2d newVector) {
        double newAngle = KrillUtil.bestAngle(newVector.getDirection() - getRadarHeadingRadians());
        setTurnRadarRightRadians(newAngle);
    }
    
    public Point2d getLocation() {
        return new Point2d(getX(), getY());
    }
    
    Object readObject(String fname) {
        try {
            ObjectInputStream ois = new ObjectInputStream(new GZIPInputStream(new FileInputStream(getDataFile(fname))));
            return ois.readObject();
        } catch (Exception e) {
            return new int[5][5][5][5][5][(int)23D];
        }
    }
    
    public void saveData(Object data, String fname) {
        try {
            ObjectOutputStream  oos = new ObjectOutputStream(new GZIPOutputStream(new RobocodeFileOutputStream(getDataFile(fname))));
            oos.writeObject(data);
        } catch (IOException ex) {
        }
    }
    
    public void onPaint(java.awt.Graphics2D g) {
        Iterator it = shapeList.iterator();
        while(it.hasNext()) {
            DebugShape shape = (DebugShape)it.next();
            g.setColor(shape.color);
            g.draw(shape.shape);
        }
        shapeList = new ArrayList();
    }
    
    public void adjustRadar(double oppBearing) {
        if (KrillUtil.fixAngle(oppBearing - this.getRadarHeading()
                              + this.getHeading(), 1) < 0) {
            _trackBackAmount = Math.abs(KrillUtil.fixAngle(oppBearing - this.getRadarHeading()
                              + this.getHeading(), 1)) + _trackAmount + 20.0;
        }
    }
    
    public void printStats() {
        out.println("Hits: "+hits);
        out.println("Misses: "+(fires-hits));
        out.println("Total: "+fires);
        out.println("Hit Ratio: "+(hits/fires));
    }
}
